<?php

/**
 * Created by PhpStorm.
 * User: huangkui@lepu.cn
 * Date: 2020/5/23
 * Time: 11:25
 */
class MyListenerConnection
{
    private $bev, $base;

    public function __destruct()
    {
        //将读写和异常回调清空同时释放BufferEvent相关内置的数据
        $this->bev->free();
    }

    public function __construct($base, $fd)
    {
        $this->base = $base;

        //创建BufferEvent对象
        //此对象内置了读写事件处理器，但并没有添加到I/O事件池中
        //同时该对象分别创建input/outpu对象【内置创建】主要用于数据读写【接收和发送】
        $this->bev = new EventBufferEvent($base, $fd, EventBufferEvent::OPT_CLOSE_ON_FREE);

        //设置读写异常回调函数 【写回调并未设置】
        $this->bev->setCallbacks(array($this, "echoReadCallback"), NULL,
            array($this, "echoEventCallback"), NULL);

        //将内置的写事件处理器添加到I/O事件池中，并且向内核事件表注册读就绪事件
        if (!$this->bev->enable(Event::READ)) {
            echo "Failed to enable READ\n";
            return;
        }
    }

    public function echoReadCallback($bev, $ctx)
    {
        //读就绪事件发生后，内置的读事件处理器运行，然后运行此函数
        //同时调用output，并把input【内置的读事件处理器读取的数据会放入到此input对象中】
        //直接将接受的数据写入到客户端
        $bev->output->addBuffer($bev->input);

    }

    public function echoEventCallback($bev, $events, $ctx)
    {
        //异常回调
        if ($events & EventBufferEvent::ERROR) {
            echo "Error from bufferevent\n";
        }

        if ($events & (EventBufferEvent::EOF | EventBufferEvent::ERROR)) {
            //$bev->free();
            $this->__destruct();
        }
    }
}

class MyListener
{
    public $base, $listener, $socket;
    private $conn = array();

    public function __destruct()
    {
        foreach ($this->conn as &$c) $c = NULL;
    }

    public function __construct($port)
    {
        //创建event_base对象
        //内置了I/O事件处理器池和信号事件处理器池
        //同时也内置的定时时间堆
        $this->base = new EventBase();
        if (!$this->base) {
            echo "Couldn't open event base";
            exit(1);
        }

        //创建socket 并监听同时将此socket的读就绪事件注册到【经过I/O复用函数即事件多路分发器EventDemultiplexer管理】
        //此socket 内置了监听事件处理器，客户端连接后，会调用此事件处理器，然后再运行用户设置的回调函数acceptConnCallBack函数
        //EventListener::OPT_CLOSE_ON_FREE | EventListener::OPT_REUSEABLE 标志位
        //EventListener::OPT_CLOSE_ON_FREE 此参数会关闭低层连接socket
        //EventListener::OPT_REUSEABLE 和前面说过的socket 选项有关【不清楚请翻阅之前我写过的东西】
        //后面2个参数为ip和端口用于生成socket
        $this->listener = new EventListener($this->base,
            array($this, "acceptConnCallback"), $this->base,
            EventListener::OPT_CLOSE_ON_FREE | EventListener::OPT_REUSEABLE, -1,
            "0.0.0.0:$port");

        if (!$this->listener) {
            echo "Couldn't create listener";
            exit(1);
        }

        //设置此socket事件处理器的错误回调
        $this->listener->setErrorCallback(array($this, "accept_error_cb"));
    }

    public function dispatch()
    {
        //内置了event_base_loop进行循环处理
        //主要是调用如epoll的epoll_wait函数进行监听
        //当任意I/O产生了就绪事件则会通知此进程
        //此进程将会遍历就绪的I/O事件读取文件描述符
        //并从I/O事件处理器池读取对应的事件处理器队链
        //再将事件处理器插入到请求队列中
        //两从请求队列中获取到事件并循环一一处理
        //从而运行指定的回调函数
        $this->base->dispatch();
    }

    /**
     * @param $listener 上面的监听器
     * @param $fd 产生就绪事件的文件描述符
     * @param $address 客户端地址
     * @param $ctx 用户自定义传递的参数
     */
    public function acceptConnCallback($listener, $fd, $address, $ctx)
    {

        $base = $this->base;
        $this->conn[] = new MyListenerConnection($base, $fd);
    }

    public function accept_error_cb($listener, $ctx)
    {
        $base = $this->base;

        fprintf(STDERR, "Got an error %d (%s) on the listener. "
            . "Shutting down.\n",
            EventUtil::getLastSocketErrno(),
            EventUtil::getLastSocketError());

        $base->exit(NULL);
    }
}

$port = 8002;

if ($argc > 1) {
    $port = (int)$argv[1];
}
if ($port <= 0 || $port > 65535) {
    exit("Invalid port");
}

$l = new MyListener($port);
//event_base_loop持续阻塞
//直到内核事件表中的I/O事件就绪产生才会运行相对的回调函数
$l->dispatch();